home *** CD-ROM | disk | FTP | other *** search
/ Amiga Collections: Taifun / Taifun 056 (1988-05-15)(Ossowski, Stefan)(DE)(PD).zip / Taifun 056 (1988-05-15)(Ossowski, Stefan)(DE)(PD).adf / Dfc / DFC.c < prev    next >
C/C++ Source or Header  |  1988-04-13  |  39KB  |  1,283 lines

  1. /*
  2.  *   This is dfc, a disk copying and formatting utility written by Radical
  3.  *   Eye Software.  This program is in the public domain; anyone can do
  4.  *   anything they want with it.  This software is made available on an as-is
  5.  *   basis; don't come to me if you destroy your entire fish disk library
  6.  *   with it!  Of course, it was tested rather extensively before it was
  7.  *   released . . .
  8.  *
  9.  *   This code was written with Manx 3.4b on February 8, 1988.
  10.  *
  11.  *   dfc is meant to replace both format and diskcopy, and be smaller than
  12.  *   either.  It is meant to work exactly the same as either, depending on
  13.  *   the command line arguments you give it.  In addition, it has several
  14.  *   additional options, such as buffering of a disk for a quick additional
  15.  *   copy, the ability to toggle verify mode on and off, and the ability to
  16.  *   write to multiple disks at the same time.  It also has a nice Intuition
  17.  *   interface.  It will not replace the `DiskCopy' or `Format' options from
  18.  *   the workbench, however; these have to work with the Workbench startup
  19.  *   message, which this program does not.
  20.  *
  21.  *   This program can be invoked with no command line options.  In this case,
  22.  *   it defaults to a diskcopy from df0: to df1: with verify on and buffering
  23.  *   on.  All keyword options can be preceded by a dash, for you Unix lovers
  24.  *   out there, and can be abbreviated to one character.  The following
  25.  *   parameters are accepted:
  26.  *
  27.  *   f[rom] disk       Use `disk' as the source drive.  The format for disk
  28.  *                     is completely free; only the numerals in the argument
  29.  *                     are looked at.
  30.  *   t[o] disks        Use `disks' as the destination drives.  Again, the
  31.  *                     format is unspecified.
  32.  *   v[erify]          Verify all writes (default).
  33.  *   nov[erify]        Turn off the verify mode.
  34.  *   b[uffer]          Use extra buffer memory.  If your system has enough
  35.  *                     memory, an entire disk will be cached in RAM.
  36.  *                     Otherwise, almost all of the system memory will be
  37.  *                     used.
  38.  *   nob[uffer]        Do not buffer; use minimum memory.  (Default.)
  39.  *   n[ame] diskname   Format the destination disk with name `diskname'.
  40.  *                     Otherwise a diskcopy is assumed.
  41.  *   d[...]            A drive argument.  Might not have a d; simple numbers
  42.  *                     work as well.  The first occurance of such an argument
  43.  *                     sets the source and destination fields; a subsequent
  44.  *                     occurance only sets the destination.
  45.  *
  46.  *   Thus,
  47.  *      dfc df0: name "Foo bar baz"
  48.  *   formats drive df0: and names the resultant disk "Foo bar baz".
  49.  *      dfc df0: df1:
  50.  *   diskcopies from df0: to df1:.
  51.  *      dfc -nov 0 123
  52.  *   diskcopies from drive 0 to drives 1, 2, and 3, with verify turned off.
  53.  *      dfc from df0: to df0:,df1:,df2:,df3:
  54.  *   diskcopies from drive df0: to all four drives.
  55.  *      dfc name Foo
  56.  *   formats drive df1 with the name "Foo".
  57.  *
  58.  *   Once the program opens its window up, you will have a bunch of gadgets.
  59.  *   The left-most column of gadgets is the source selection; here you can
  60.  *   choose either format, or one of the four drives.  The next column is
  61.  *   the destination column; choose any combination of the four drives by
  62.  *   clicking on the gadgets.  Lines will be drawn between these two columns
  63.  *   indicating the current selections.  If a drive is not available, no line
  64.  *   will be drawn to it.
  65.  *
  66.  *   The topmost gadget is the name gadget.  This is used to set the name of
  67.  *   your disk while formatting.
  68.  *
  69.  *   The `Go' gadget starts the program up; you had better have the disks in
  70.  *   the drives when you hit this gadget!  The `Again' gadget makes another
  71.  *   copy of the last disk, if buffering is on and enough buffers were
  72.  *   allocated for an entire disk.  This gadget requires all destination
  73.  *   disks to be in their drives already.
  74.  *
  75.  *   The `Retry' gadget tells the system to retry after a disk error.  The
  76.  *   `Quit' gadget tells the system to abort an operation; you can hit this
  77.  *   at an error, which aborts the current operation; you can hit this during
  78.  *   a copy or format operation, which does the same; or you can hit this
  79.  *   with the drives inactive, which exits the program.
  80.  *
  81.  *   The `Verify' gadget turns the verify mode on and off.  The `Buffer'
  82.  *   gadget turns buffering on and off.
  83.  *
  84.  *   All of these gadgets have keyboard shortcuts which are simply the first
  85.  *   letter of the gadget name.  The source gadgets can be selected with the
  86.  *   digits `0', `1', `2', or `3'; the destination gadgets with the shifted
  87.  *   versions of these keys (')', `!', `@', and `#'.)  The carriage return
  88.  *   key is a synonym for `Go', for added diskcopy and format compatibility.
  89.  *
  90.  *   Enjoy this program!  Please send any bug reports to Tomas Rokicki,
  91.  *   Box 2081, Stanford, CA  94309.
  92.  */
  93. #define TITLE "dfc 1 Radical Eye Software"
  94. /*
  95.  *   A handful of includes for good luck.
  96.  */
  97. #include "intuition/intuition.h"
  98. #include "functions.h"
  99. #include <exec/exec.h>
  100. #include <exec/execbase.h>
  101. #include <devices/trackdisk.h>
  102. #include <libraries/dosextens.h>
  103. /*
  104.  *   These are the various globals this routine uses.
  105.  */
  106. struct StandardPacket *gpacket ;   /* packet to send various things */
  107. struct IntuiMessage *message ;     /* the message we are working on */
  108. struct Gadget *gadad ;             /* address of gadget from message */
  109. struct Window *window ;            /* our window */
  110. struct MsgPort *port ;             /* I/O port for dos communication */
  111. struct IntuitionBase *IntuitionBase ;
  112.                                    /* we need Intuition */
  113. struct GfxBase *GfxBase ;          /* and graphics */
  114. int havedisk ;                     /* do we have a full disk in RAM? */
  115. int hibuf ;                        /* the highest buffer we have */
  116. long output ;                      /* can we write output? */
  117. char *buffers[81] ;                /* pointers to our buffers */
  118. struct IOExtTD *diskreq[4] ;       /* our I/O request blocks */
  119. long diskChangeCount[4] ;          /* last time disk was changed */
  120. int source = 1 ;                   /* the source disk, not a mask */
  121. int dest = 2 ;                     /* all destination drives */
  122. int verify = 1 ;                   /* verify writes? */
  123. int buffer = 0 ;                   /* are we using lots of memory? */
  124. char namebuf[32] ;                 /* this buffer holds disk names */
  125. char df0[] = "DF0:" ;              /* use only one copy of this name */
  126. char df1[] = "DF1:" ;              /* ditto */
  127. char df2[] = "DF2:" ;              /* ditto */
  128. char df3[] = "DF3:" ;              /* ditto */
  129. char *df[]={df0, df1, df2, df3};   /* for easy access to drive names */
  130. char blank[] =   "                    " ;
  131.                                    /* used to blank out the middle line */
  132. char msg[] = "Disk df  is not complete" ;
  133.                                    /* error message for a disk */
  134. char reading[] = "Reading track xx dfx" ;
  135.                                    /* message for reading disks */
  136. char writing[] = "Writing track xx dfx" ;
  137.                                    /* message for writing disks */
  138. char veriing[] = "Ver'ing track xx dfx" ;
  139.                                    /* message for ver'ing disks */
  140. char nobuf[] = "! couldn't get buffer" ;
  141.                                    /* if we can't get a buffer */
  142. /*
  143.  *   Always use topaz 80, and let's define an intuition buffer for our
  144.  *   general string writing stuff.
  145.  */
  146. struct TextAttr myfont = {(STRPTR)"topaz.font", TOPAZ_EIGHTY, 0, 0 };
  147. struct IntuiText intuitext = {1, 0, JAM2, 0, 0, &myfont} ;
  148. /*
  149.  *   Some defines setting the sizes of various things.  Play with these to
  150.  *   resize things.  Too bad string gadgets still have problems with large
  151.  *   fonts.
  152.  */
  153. #define WIDEGADG 83
  154. #define NARROWGADG 51
  155. #define STRGADG 256
  156. #define GADGHEIGHT 13
  157. #define LINE1 13
  158. #define LINE2 LINE1+11
  159. #define LINE3 LINE2+GADGHEIGHT
  160. #define LINE4 LINE3+GADGHEIGHT
  161. #define LINE5 LINE4+GADGHEIGHT
  162. #define LINE6 LINE5+GADGHEIGHT
  163. #define WINDOWHEIGHT LINE6+GADGHEIGHT+2
  164. #define COL1 4
  165. #define LINESTART COL1+NARROWGADG
  166. #define STRSTART COL1+NARROWGADG+23
  167. #define COL2 COL1+NARROWGADG+42
  168. #define LINEEND COL2-1
  169. #define COL3 COL2+NARROWGADG+10
  170. #define COL4 COL3+WIDEGADG+10
  171. #define WINDOWWIDTH COL4+WIDEGADG+4
  172. #define TRACKSIZE (2*512*11)
  173. /*
  174.  *   Now, some defines to assist in declaring a few things, and
  175.  *   centering strings, and the like.  These macros make the Intuition
  176.  *   crap a lot easier to put together.
  177.  */
  178. #define makeintuitext(name,string,size) struct IntuiText name={1,0,JAM2,\
  179.    (1+size-8*(sizeof(string)-1))/2,3,&myfont,(UBYTE*)string}
  180. #define makebox(name,tname,width,height,off) short tname[]={0,0,width,0,\
  181.    width,1,0,1,0,height-1,width,height-1,width,height,0,height,width,height,\
  182.    width,0};\
  183.    struct Border name={off,off,1,0,JAM2,10,tname}
  184. #define makengadg(name,prev,xpos,ypos,text,ch) struct Gadget name={prev,\
  185.    xpos,ypos,NARROWGADG,GADGHEIGHT,GADGHCOMP,RELVERIFY,BOOLGADGET,\
  186.    (APTR)&narrowbox,NULL,&text,NULL,NULL,ch}
  187. #define makewgadg(name,prev,xpos,ypos,text,ch) struct Gadget name={prev,\
  188.    xpos,ypos,WIDEGADG,GADGHEIGHT,GADGHCOMP,RELVERIFY,BOOLGADGET,\
  189.    (APTR)&widebox,NULL,&text,NULL,NULL,ch}
  190. /*
  191.  *   Now we declare all of our structures with the above macros.
  192.  *   First, the text for our gadgets, and then the gadgets.  No sweat.
  193.  */
  194. makeintuitext(formattext,"Format",NARROWGADG);
  195. makeintuitext(df0text,df0,NARROWGADG);
  196. makeintuitext(df1text,df1,NARROWGADG);
  197. makeintuitext(df2text,df2,NARROWGADG);
  198. makeintuitext(df3text,df3,NARROWGADG);
  199. makeintuitext(againtext,"Again",WIDEGADG);
  200. makeintuitext(retrytext,"Retry",WIDEGADG);
  201. makeintuitext(quittext,"Quit",WIDEGADG);
  202. makeintuitext(verifyontext,"Verify On ",WIDEGADG);
  203. makeintuitext(verifyofftext,"Verify Off",WIDEGADG);
  204. makeintuitext(bufferontext,"Buffer On ",WIDEGADG);
  205. makeintuitext(bufferofftext,"Buffer Off",WIDEGADG);
  206. makeintuitext(gotext,"Go",WIDEGADG);
  207. makebox(narrowbox,tn1,NARROWGADG-1,GADGHEIGHT-1,0);
  208. makebox(widebox,tn2,WIDEGADG-1,GADGHEIGHT-1,0);
  209. makebox(strbox,tn3,STRGADG-1,GADGHEIGHT-1,-2);
  210. makengadg(formatgadg,NULL,COL1,LINE2,formattext,'f');
  211. makengadg(sdf0gadg,&formatgadg,COL1,LINE3,df0text,'0');
  212. makengadg(sdf1gadg,&sdf0gadg,COL1,LINE4,df1text,'1');
  213. makengadg(sdf2gadg,&sdf1gadg,COL1,LINE5,df2text,'2');
  214. makengadg(sdf3gadg,&sdf2gadg,COL1,LINE6,df3text,'3');
  215. makengadg(ddf0gadg,&sdf3gadg,COL2,LINE3,df0text,')');
  216. makengadg(ddf1gadg,&ddf0gadg,COL2,LINE4,df1text,'!');
  217. makengadg(ddf2gadg,&ddf1gadg,COL2,LINE5,df2text,'@');
  218. makengadg(ddf3gadg,&ddf2gadg,COL2,LINE6,df3text,'#');
  219. makewgadg(gogadg,&ddf3gadg,COL3,LINE4,gotext,'g');
  220. makewgadg(retrygadg,&gogadg,COL3,LINE5,retrytext,'r');
  221. makewgadg(verifygadg,&retrygadg,COL3,LINE6,verifyontext,'v');
  222. makewgadg(againgadg,&verifygadg,COL4,LINE4,againtext,'a');
  223. makewgadg(quitgadg,&againgadg,COL4,LINE5,quittext,'q');
  224. makewgadg(buffergadg,&quitgadg,COL4,LINE6,bufferontext,'b');
  225. /*
  226.  *   We need one last gadget, the string gadget, and its associated
  227.  *   special info structure.
  228.  */
  229. struct StringInfo nameinfo={(UBYTE*)namebuf,NULL,0,31};
  230. struct Gadget namegadg={&buffergadg,STRSTART+2,LINE2+2,STRGADG-4,
  231.    GADGHEIGHT-4,GADGHCOMP,STRINGCENTER|RELVERIFY,STRGADGET,(APTR)&strbox,
  232.    NULL,NULL,NULL,(APTR)&nameinfo,'n'};
  233. /*
  234.  *   Now we have our window structure.  Initially not resizeable.
  235.  */
  236. struct NewWindow newwindow = {200,20,WINDOWWIDTH,WINDOWHEIGHT,0,1,
  237.    CLOSEWINDOW|VANILLAKEY|GADGETDOWN|GADGETUP,
  238.    WINDOWDEPTH|WINDOWCLOSE|WINDOWDRAG|SMART_REFRESH|ACTIVATE,
  239.    &namegadg,NULL,(UBYTE *)TITLE,NULL,NULL,-1,-1,-1,-1,WBENCHSCREEN};
  240. /*
  241.  *   Now we start coding.  This routine draws a string into the window at
  242.  *   a specific X, Y location.
  243.  */
  244. draw(s, x, y)
  245. char *s ;
  246. int x, y ;
  247. {
  248.    intuitext.IText = (UBYTE *)s ;
  249.    PrintIText(window->RPort, &intuitext, (long)x, (long)y) ;
  250. }
  251. /*
  252.  *   This routine gives us those pretty little DF0:BUSY things, which keep
  253.  *   AmigaDOS from futzing with the drives when we are playing with them.
  254.  */
  255. inhibit(d, t)
  256. int d ;
  257. long t ;
  258. {
  259.    struct MsgPort *handler ;
  260.    struct StandardPacket *packet = gpacket ;
  261.  
  262.    handler = (struct MsgPort *)DeviceProc(df[d]) ;
  263.    if (handler == NULL || port == NULL)
  264.       return ;
  265.    packet->sp_Msg.mn_Node.ln_Name = (char *)&(packet->sp_Pkt) ;
  266.    packet->sp_Pkt.dp_Link = &(packet->sp_Msg) ;
  267.    packet->sp_Pkt.dp_Port = port ;
  268.    packet->sp_Pkt.dp_Type = ACTION_INHIBIT ;
  269.    packet->sp_Pkt.dp_Arg1 = t ;
  270.    PutMsg(handler, packet) ;
  271.    WaitPort(port) ;
  272.    GetMsg(port) ;
  273. }
  274. /*
  275.  *   This routine looks at the global message and returns a character
  276.  *   indicating the selected gadget.  This gives us easy equivalence
  277.  *   of gadgets to vanillakeys, for instance.  This also replymsg()'s
  278.  *   the message.
  279.  */
  280. int getop() {
  281.    register long class ;
  282.    register int op ;
  283.    short code ;
  284.  
  285.    class = message->Class ;
  286.    code = message->Code ;
  287.    op = ' ' ;
  288.    gadad = (struct Gadget *)(message->IAddress) ;
  289.    ReplyMsg(message) ;
  290.    message = NULL ;
  291.    if (class == CLOSEWINDOW) {
  292.       op = 'q' ;
  293.    } else if (class == GADGETDOWN || class == GADGETUP) {
  294.       op = gadad->GadgetID ;
  295.    } else if (class == VANILLAKEY) {
  296.       op = code ;
  297.       gadad = NULL ;
  298.    }
  299.    return(upcase(op)) ;
  300. }
  301. /*
  302.  *   We don't want to queue up messages, because the user might hit
  303.  *   'g' 100 times accidentally.  We flush all pending messages, and
  304.  *   return the last operation.  This way, this routine can be used
  305.  *   to see if the user typed 'Q' to exit some operation.
  306.  */
  307. int disposemsgs() {
  308.    register int op = 0 ;
  309.  
  310.    while (message = (struct IntuiMessage *)
  311.                         GetMsg(window->UserPort))
  312.       op = getop() ;
  313.    return(op) ;
  314. }
  315. /*
  316.  *   This is put into a function to help make the program smaller.
  317.  *   It takes a character and makes sure it is uppercase.
  318.  */
  319. int upcase(c)
  320. register int c ;
  321. {
  322.    if ('a' <= c && c <= 'z')
  323.       return(c-32) ;
  324.    else
  325.       return(c) ;
  326. }
  327. /*
  328.  *   Here we wait for a quit or retry key.  We also accept go, space,
  329.  *   and carriage return.
  330.  */
  331. int abortretry() {
  332.    register int op ;
  333.  
  334.    disposemsgs() ;
  335.    while (1) {
  336.       while ((message = (struct IntuiMessage *)
  337.                      GetMsg(window->UserPort))==NULL)
  338.          WaitPort(window->UserPort) ;
  339.       op = getop() ;
  340.       if (op == 'Q' || op == 'R' || op == ' ' || op == 'G' || op == 10)
  341.          return(op) ;
  342.    }
  343. }
  344. /*
  345.  *   This routine pads a string out to 40 characters.  Used to write to
  346.  *   the top line of the window.
  347.  */
  348. static char ibuf[41] ;
  349. char *to40(s)
  350. register char *s ;
  351. {
  352.    register int i = 0 ;
  353.    register char *p = ibuf ;
  354.  
  355.    while (*s != 0)
  356.       p[i++] = *s++ ;
  357.    while (i < 40)
  358.       p[i++] = ' ' ;
  359.    return(p) ;
  360. }
  361. /*
  362.  *   This error routine draws a message up at the top of the screen, and
  363.  *   waits for a response.  After it gets one, it returns the response.
  364.  *   It clears the top line afterwards.
  365.  */
  366. int error(s)
  367. register char *s ;
  368. {
  369.    int op ;
  370.  
  371.    DisplayBeep(NULL) ;
  372.    if (*s == '!' || !window) {
  373.       if (output) {
  374.          Write(output, s, (long)strlen(s)) ;
  375.          Write(output, "\n", 1L) ;
  376.       }
  377.       cleanup() ;
  378.    } else {
  379.       draw(to40(s), COL1, LINE1+2) ;
  380.       op = abortretry() ;
  381.    }
  382.    draw(to40(""), COL1, LINE1+2) ;
  383.    return(op) ;
  384. }
  385. /*
  386.  *   If the user selects a set of drives, this routine first tries to
  387.  *   allocate them.  Then, based on the success of the allocation, it
  388.  *   draws lines in the window indicating the source and destination
  389.  *   drives.
  390.  */
  391. redrawlines() {
  392.    register int i, j ;
  393.  
  394.    allocdisks() ;
  395.    SetAPen(window->RPort, 0L) ;
  396.    RectFill(window->RPort, (long)LINESTART, (long)LINE2, (long)STRSTART-1,
  397.                            (long) LINE6+GADGHEIGHT) ;
  398.    RectFill(window->RPort, (long)STRSTART, (long)LINE3, (long)LINEEND,
  399.                            (long)LINE6+GADGHEIGHT) ;
  400.    SetAPen(window->RPort, 1L) ;
  401.    i = LINE3 + (GADGHEIGHT+1)/2 + source * GADGHEIGHT ;
  402.    for (j=0; j<4; j++)
  403.       if (dest & (1 << j)) {
  404.          Move(window->RPort, (long)LINESTART, (long)i) ;
  405.          Draw(window->RPort, (long)LINEEND,
  406.                          (long)(LINE3 + (GADGHEIGHT+1)/2 + j * GADGHEIGHT)) ;
  407.       }
  408. }
  409. /*
  410.  *   This routine parses a string, usually something like 'df0:' or
  411.  *   'df0:,df1:,df2:', but can be even '012', into just a mask indicating
  412.  *   which drives were selected.  It simply looks for the characters '0',
  413.  *   '1', '2', and '3'.
  414.  */
  415. int getmask(s)
  416. register char *s ;
  417. {
  418.    register int t = 0 ;
  419.  
  420.    while (*s != 0) {
  421.       if ('0' <= *s && *s <= '3')
  422.          t |= 1 << (*s - '0') ;
  423.       s++ ;
  424.    }
  425.    return(t) ;
  426. }
  427. /*
  428.  *   This is our exit routine.  It frees the drives, deletes the ports,
  429.  *   buffers, closes the window, and libraries.  Then it exits.
  430.  */
  431. cleanup() {
  432.    register int i ;
  433.  
  434.    for (i=0; i<4; i++)
  435.       FreeDisk(i) ;
  436.    if (gpacket)
  437.       FreeMem(gpacket, (long)sizeof(struct StandardPacket)) ;
  438.    if (port)
  439.       DeletePort(port) ;
  440.    freebuffers() ;
  441.    if (window)
  442.       CloseWindow(window) ;
  443.    if (GfxBase)
  444.       CloseLibrary(GfxBase) ;
  445.    if (IntuitionBase)
  446.       CloseLibrary(IntuitionBase) ;
  447.    exit(0) ;
  448. }
  449. /*
  450.  *   This routine attempts to allocate m buffers.  We try and leave at
  451.  *   least 32K of memory, even with the allocations.  We set hibuf at the
  452.  *   exit point.
  453.  */
  454. getbuffers(m)
  455. int m ;
  456. {
  457.    register int i ;
  458.  
  459.    if (buffers[80]==NULL &&
  460.       (buffers[80]=(char *)
  461.               AllocMem((long)TRACKSIZE, MEMF_CHIP | MEMF_PUBLIC))==NULL)
  462.       error(nobuf) ;
  463.    hibuf = 0 ;
  464.    for (i=0; i<m; i++)
  465.       if (buffers[i]==NULL &&
  466.          (AvailMem(MEMF_PUBLIC) < 32000 ||
  467.          (buffers[i]=(char *)AllocMem((long)TRACKSIZE, MEMF_PUBLIC))==NULL))
  468.             break ;
  469.       else
  470.          hibuf = i+1 ;
  471.    if (hibuf < 1)
  472.       error(nobuf) ;
  473.    for (; i<80; i++)
  474.       if (buffers[i] != NULL) {
  475.          FreeMem(buffers[i], (long)TRACKSIZE) ;
  476.          buffers[i] = NULL ;
  477.       }
  478. }
  479. /*
  480.  *   And this routine lets them all go.
  481.  */
  482. int freebuffers() {
  483.    register int i ;
  484.  
  485.    for (i=0; i<81; i++)
  486.       if (buffers[i] != NULL) {
  487.          FreeMem(buffers[i], (long)TRACKSIZE) ;
  488.          buffers[i] = NULL ;
  489.       }
  490. }
  491. /*
  492.  *   We attempt to create an I/O request.  We allocate memory for it, and
  493.  *   then initialize some of the ports.
  494.  */
  495. struct IORequest *CreatExtIO() {
  496.    register struct IORequest *ioReq ;
  497.  
  498.    ioReq = (struct IORequest *)AllocMem((long)sizeof(struct IOExtTD),
  499.                                         MEMF_CLEAR | MEMF_PUBLIC) ;
  500.    if (ioReq == NULL)
  501.       return (NULL) ;
  502.    ioReq->io_Message.mn_Node.ln_Type = NT_MESSAGE ;
  503.    ioReq->io_Message.mn_Node.ln_Pri = 0 ;
  504.    ioReq->io_Message.mn_ReplyPort = port ;
  505.    return(ioReq) ;
  506. }
  507. /*
  508.  *   This routine frees an I/O request.
  509.  */
  510. DeleteIO(ioExt)
  511. struct IORequest *ioExt ;
  512. {
  513.    FreeMem(ioExt, (long)sizeof(struct IOExtTD)) ;
  514. }
  515. /*
  516.  *   Now we try to allocate a disk.  First, we attempt to allocate an
  517.  *   I/O request, and then we actually open the device.  If either of
  518.  *   these fail, we return 0.  This can occur if a drive is opened that
  519.  *   doesn't exist.  After we have the disk, we inhibit AmigaDOS from
  520.  *   putzing with it.
  521.  */
  522. int OpenDisk(i)
  523. register int i ;
  524. {
  525.    register struct IOExtTD **p = diskreq+i ;
  526.  
  527.    if (*p)
  528.       return(1) ;
  529.    if ((*p = (struct IOExtTD *)CreatExtIO()) &&
  530.        OpenDevice(TD_NAME, (long)i, *p, 0L) == 0) {
  531.       inhibit(i, TRUE) ;
  532.       return(1) ;
  533.    } else {
  534.       if (*p) {
  535.          DeleteIO(*p) ;
  536.          *p = NULL ;
  537.       }
  538.       return(0) ;
  539.    }
  540. }
  541. /*
  542.  *   Here we release a disk.  We check that we have it first!  Then, we
  543.  *   close the device, uninhibit the drive, kill the I/O request, and
  544.  *   exit.
  545.  */
  546. FreeDisk(i)
  547. register int i ;
  548. {
  549.    register struct IOExtTD **p = diskreq+i ;
  550.  
  551.    if (*p) {
  552.       CloseDevice(*p) ;
  553.       inhibit(i, FALSE) ;
  554.       DeleteIO(*p) ;
  555.       *p = NULL ;
  556.    }
  557. }
  558. /*
  559.  *   This routine attempts to allocate all of the disks we need, based on the
  560.  *   current settings of source and dest.  If a disk cannot be allocated, it
  561.  *   is removed from both source or dest.  Source might be set to point to df0,
  562.  *   if this occurs.  Then, if we can't allocate df0, we exit fatally.  This
  563.  *   occurance can actually happen, if some other program has df0:.  (I think.)
  564.  */
  565. allocdisks() {
  566.    register int i, need ;
  567.  
  568. top:
  569.    need = dest ;
  570.    if (source != -1)
  571.       need |= 1 << source ;
  572.    for (i=0; i<4; i++, need >>= 1) {
  573.       if (need & 1) {
  574.          if (! OpenDisk(i)) {
  575.             dest &= ~(1 << i) ;
  576.             if (source == i) {
  577.                if (i == 0)
  578.                   error("! I couldn't allocate the internal drive") ;
  579.                source = 0 ;
  580.                goto top ;
  581.             }
  582.         }
  583.       } else
  584.         FreeDisk(i) ;
  585.    }
  586. }
  587. /*
  588.  *   We call this routine if we are going to be accessing this
  589.  *   disk.  It gets the changecount, so our read/write routines work.
  590.  */
  591. int InitDisk(d)
  592. register int d ;
  593. {
  594.    register struct IOExtTD *p = diskreq[d] ;
  595.    int result ;
  596.  
  597.    p->iotd_Req.io_Command = TD_CHANGENUM ;
  598.    DoIO(p) ;
  599.    result = (diskChangeCount[d] != p->iotd_Req.io_Actual) ;
  600.    diskChangeCount[d] = p->iotd_Req.io_Actual ;
  601.    return(result) ;
  602. }
  603. /*
  604.  *   The plural of the above routine takes a mask and initializes all of
  605.  *   the drives.  It adds the source drive to the list automatically.
  606.  */
  607. initdisks(mask)
  608. int mask ;
  609. {
  610.    int d ;
  611.  
  612.    if (source != -1)
  613.       mask |= (1 << source) ;
  614.    for (d=0; d<4; d++)
  615.       if (mask & (1 << d))
  616.          InitDisk(d) ;
  617. }
  618. /*
  619.  *   Kill the motor of a drive.  So someone can stick disks in and out.
  620.  */
  621. motoroff(d)
  622. register int d ;
  623. {
  624.    register struct IOExtTD *p = diskreq[d] ;
  625.  
  626.    p->iotd_Req.io_Length = 0 ;
  627.    p->iotd_Req.io_Command = TD_MOTOR ;
  628.    DoIO(p) ;
  629. }
  630. /*
  631.  *   This routine is a fast machine-language block move, that moves
  632.  *   exactly one block of data.  Do not change TRACKSIZE and expect
  633.  *   this still to work!
  634.  */
  635. fcpy(dest, src)
  636. long *dest, *src ;
  637. {
  638. #asm
  639.     movem.l    a0-a6/d0-d7,-(a7)
  640.     move.l    12(a5),a0
  641.     move.l    8(a5),a1
  642.     move.l    #43,d0
  643. lsdf:
  644.     movem.l    (a0)+,a2-a6/d1-d7
  645.     movem.l    a2-a6/d1-d7,(a1)
  646.     add.w    #48,a1
  647.     movem.l    (a0)+,a2-a6/d1-d7
  648.     movem.l    a2-a6/d1-d7,(a1)
  649.     add.w    #48,a1
  650.     movem.l    (a0)+,a2-a6/d1-d7
  651.     movem.l    a2-a6/d1-d7,(a1)
  652.     add.w    #48,a1
  653.     movem.l    (a0)+,a2-a6/d1-d7
  654.     movem.l    a2-a6/d1-d7,(a1)
  655.     add.w    #48,a1
  656.     movem.l    (a0)+,a2-a6/d1-d7
  657.     movem.l    a2-a6/d1-d7,(a1)
  658.     add.w    #48,a1
  659.     movem.l    (a0)+,a2-a5
  660.     movem.l    a2-a5,(a1)
  661.     add.w    #16,a1
  662.     dbra    d0,lsdf
  663.     movem.l    (a7)+,a0-a6/d0-d7
  664. #endasm
  665. }
  666. /*
  667.  *   Another fast assembly language routine for verifying a buffer.  This
  668.  *   routine returns 0 if the two buffers are the same, and something else
  669.  *   otherwise.
  670.  */
  671. int fcmp(dest, src)
  672. long *dest, *src ;
  673. {
  674.     register int foo = 0 ;
  675. #asm
  676.     movem.l    d0/a0/a1,-(a7)
  677.     move.l    12(a5),a0
  678.     move.l    8(a5),a1
  679.     move.l    #2815,d0
  680. alsdf:
  681.     cmp.l    (a0)+,(a1)+
  682.     dbne    d0,alsdf
  683.     move.w    d0,d4
  684.     addq.w    #1,d4
  685.     movem.l    (a7)+,a0/a1/d0
  686. #endasm
  687.    return(foo) ;
  688. }
  689. /*
  690.  *   This routine turns on a particular gadget.
  691.  */
  692. turnon(g)
  693. register struct Gadget *g ;
  694. {
  695.    if (g->Flags & GADGDISABLED) {
  696.       SetAPen(window->RPort, 0L) ;
  697.       RectFill(window->RPort, (long)g->LeftEdge, (long)g->TopEdge,
  698.                               (long)g->LeftEdge+g->Width-1,
  699.                               (long)g->TopEdge+g->Height-1) ;
  700.       SetAPen(window->RPort, 1L) ;
  701.       OnGadget(g, window, NULL) ;
  702.    }
  703. }
  704. /*
  705.  *   This routine turns off a particular gadget.
  706.  */
  707. turnoff(g)
  708. register struct Gadget *g ;
  709. {
  710.    if (!(g->Flags & GADGDISABLED))
  711.       OffGadget(g, window, NULL) ;
  712. }
  713. /*
  714.  *   Our main copy routine.  The variable j holds the current destinations
  715.  *   that are being written into; as disks drop like flies, j will drop
  716.  *   them; at the end, we print a message about all the disks that dropped
  717.  *   out.  We start by initializing the disks.
  718.  */
  719. goforit(again)
  720. int again ;
  721. {
  722.    register int i, j, k, t ;
  723.    register int ohbuf ;
  724.  
  725.    turnoff(&gogadg) ;
  726.    turnoff(&verifygadg) ;
  727.    turnoff(&buffergadg) ;
  728.    turnoff(&againgadg) ;
  729.    turnon(&retrygadg) ;
  730.    turnoff(&namegadg) ;
  731.    RefreshGadgets(window->FirstGadget, window, NULL) ;
  732.    j = dest ;
  733.    initdisks(j) ;
  734.    ohbuf = hibuf ;
  735. /*
  736.  *   If we are formatting, we only use one buffer.  This avoids the
  737.  *   unsightly delay which happens if we build up the formatted disk in
  738.  *   memory first; the user wonders what the hell is going on.  We first
  739.  *   check that the user isn't trying to read and write from the same disk;
  740.  *   if he is, he will have to swap about 160 times, so we tell him no go.
  741.  */
  742.    if (source == -1)
  743.       ohbuf = 1 ;
  744.    if (ohbuf == 1 && source >= 0 && (dest & (1 << source))) {
  745.       while (error("No buffering?") == 'R') ;
  746.       goto finishup ;
  747.    }
  748.    for (t=0; t<80; t+=ohbuf) {
  749.       if (! again) {
  750.          for (k=0; k<ohbuf && t+k<80; k++) {
  751.             if (disposemsgs()=='Q')
  752.                goto aborted ;
  753.             if (getdata(source, t+k, k)==0)
  754.                goto finishup ;
  755.          }
  756.          if (source != -1) {
  757.             if (dest & (1 << source)) {
  758.                for (i=0; i<4; i++)
  759.                   if (j & (1 << i))
  760.                      motoroff(i) ;
  761.                do {
  762.                   if (error("Enter destination disks")=='Q')
  763.                      goto finishup ;
  764.                } while (! InitDisk(source)) ;
  765.                initdisks(j) ;
  766.             }
  767.          }
  768.          if (ohbuf == 80)
  769.             havedisk = 1 ;
  770.       }
  771.       for (k=0; k<ohbuf && t+k<80; k++) {
  772.          if (disposemsgs()=='Q')
  773.             goto aborted ;
  774.          for (i=0; i<4; i++) {
  775.             if (j & (1 << i)) {
  776.                if (writedata(i, t+k, k)==0) {
  777.                   j &= ~(1 << i) ;
  778.                   motoroff(i) ;
  779.                }
  780.             }
  781.          }
  782.       }
  783.       if (! again) {
  784.          if (source != -1 && (dest & (1 << source)) && t+k < 80) {
  785.             motoroff(source) ;
  786.             do {
  787.                if (error("Enter source disk")=='Q')
  788.                   goto finishup ;
  789.             } while (! InitDisk(source)) ;
  790.          }
  791.       }
  792.    }
  793.    goto finishup ;
  794. /*
  795.  *   On exit, we turn off all the motors, clear out the middle line, and
  796.  *   return.
  797.  */
  798. aborted:
  799.    j = 0 ;
  800. finishup:
  801.    for (i=0; i<4; i++)
  802.       if (diskreq[i])
  803.          motoroff(i) ;
  804.    for (i=0; i<4; i++)
  805.       if ((dest-j) & (1<<i)) {
  806.          msg[7] = i + '0' ;
  807.          error(msg) ;
  808.       }
  809.    draw(blank, COL3, LINE3+3) ;
  810.    disposemsgs() ;
  811. }
  812. /*
  813.  *   This routine writes a given message to the screen; either reading,
  814.  *   writing, or ver'ing.  It fills in the track and disk number.  It
  815.  *   checks first that the source isn't `format', which is created behind
  816.  *   the scenes instead of being read from an actual disk.  If we are
  817.  *   reading from track 40, we get the name of the disk and put it up on
  818.  *   the screen.
  819.  */
  820. writeop(s, t, d)
  821. register char *s ;
  822. register int t, d ;
  823. {
  824.    if (d != -1) {
  825.       s[14] = '0' + t / 10 ;
  826.       s[15] = '0' + t % 10 ;
  827.       s[19] = '0' + d ;
  828.       draw(s, COL3, LINE3+3) ;
  829.    }
  830. }
  831. /*
  832.  *   This routine gets data from disk d, track t, into buffer b.
  833.  *   If the disk is -1, it gets it from the format routine (later.)
  834.  *   We return 1 if success; 0 if failure.  If there is an error, we allow
  835.  *   the user to retry as many times as he likes.
  836.  */
  837. int getdata(d,t,b)
  838. register int d, t, b ;
  839. {
  840.    register struct IOExtTD *p = diskreq[d] ;
  841.    register int i = 0 ;
  842.  
  843.    if (d==-1) {
  844.       makeformatdata(t, b) ;
  845.       return(1) ;
  846.    }
  847.    do {
  848.       writeop(reading, t, d) ;
  849.       p->iotd_Req.io_Length = TRACKSIZE ;
  850.       p->iotd_Req.io_Data = (APTR)buffers[80] ;
  851.       p->iotd_Req.io_Command = ETD_READ ;
  852.       p->iotd_Count = diskChangeCount[d] ;
  853.       p->iotd_Req.io_Offset = t * (long)TRACKSIZE ;
  854.       DoIO(p) ;
  855.       fcpy(buffers[b], buffers[80]) ;
  856.    } while (p->iotd_Req.io_Error != 0 &&
  857.             ((i=error("Read Error; Quit/Retry?"))!='Q')) ;
  858.    if (t==40 && i != 'Q')
  859.       writename(b) ;
  860.    return (i != 'Q') ;
  861. }
  862. /*
  863.  *   writedata is analagous to the above routine.  However, if verify is
  864.  *   turned on, then we read the data back in from the disk and make sure
  865.  *   that it is correct.  Note that whenever we write track 40, we first
  866.  *   update the root block creation date and last modified date.
  867.  */
  868. int writedata(d,t,b)
  869. register int d, t, b ;
  870. {
  871.    register struct IOExtTD *p = diskreq[d] ;
  872.    register int i = 0 ;
  873.  
  874. top:
  875.    if (t==40)
  876.       updaterootblock(b) ;
  877.    do {
  878.       writeop(writing, t, d) ;
  879.       fcpy(buffers[80], buffers[b]) ;
  880.       p->iotd_Req.io_Length = TRACKSIZE ;
  881.       p->iotd_Req.io_Data = (APTR)buffers[80] ;
  882.       p->iotd_Req.io_Command = TD_FORMAT ;
  883.       p->iotd_Count = diskChangeCount[d] ;
  884.       p->iotd_Req.io_Offset = t * (long)TRACKSIZE ;
  885.       DoIO(p) ;
  886.    } while (p->iotd_Req.io_Error != 0 &&
  887.             ((i=error("Write Error; Quit/Retry?"))!='Q')) ;
  888.    if (i=='Q')
  889.       return(0) ;
  890.    if (verify) {
  891.       writeop(veriing, t, d) ;
  892.       p->iotd_Req.io_Length = TRACKSIZE ;
  893.       p->iotd_Req.io_Data = (APTR)buffers[80] ;
  894.       p->iotd_Req.io_Command = ETD_READ ;
  895.       p->iotd_Count = diskChangeCount[d] ;
  896.       p->iotd_Req.io_Offset = t * (long)TRACKSIZE ;
  897.       DoIO(p) ;
  898.       if ((p->iotd_Req.io_Error != 0 ||
  899.           fcmp(buffers[80], buffers[b])) &&
  900.           ((i=error("Verify Error; Quit/Retry?"))!='Q')) {
  901.          if (t==0)
  902.             InitDisk(d) ;
  903.          goto top ;
  904.       }
  905.    }
  906.    return (i != 'Q') ;
  907. }
  908. /*
  909.  *   This routine creates data for the format option.  Note the clever
  910.  *   way the data is built up; this routine should build a disk exactly
  911.  *   the same way the standard AmigaDOS format does.  If we are on track
  912.  *   40, some additional work must be done to create a root block and
  913.  *   bitmap, but it's not too bad.
  914.  */
  915. makeformatdata(t, b)
  916. int t, b ;
  917. {
  918.    register long *p ;
  919.    register long cs ;
  920.    register long i ;
  921.    unsigned char *q ;
  922.  
  923.    p = (long *)buffers[b] ;
  924.    cs = 'DOS\0' + (((long)t & 48) << 16) ;
  925.    for (i=0; i<TRACKSIZE/4; i++)
  926.       *p++ = cs + (i & 3327) ;
  927.    if (t != 40)
  928.       return ;
  929.    p = (long *)buffers[b] ;
  930.    for (i=0; i<256; i++)
  931.       *p++ = 0 ;
  932.    p = (long *)buffers[b] ;
  933.    p[0] = 2 ;
  934.    p[3] = 0x48 ;
  935.    p[78] = 1 ;
  936.    p[79] = 0x371 ;
  937.    q = (unsigned char *)(p + 108) ;
  938.    *q++ = strlen(namebuf) ;
  939.    strcpy(q, namebuf) ;
  940.    p[127] = 1 ;
  941.    p += 128 ;
  942.    for (i=1; i<55; i++)
  943.       p[i] = 0xffffffff ;
  944.    p[0] = 0xc000c037 ;
  945.    p[28] = 0xffff3fff ;
  946.    p[55] = 0x3fffffff ;
  947. }
  948. /*
  949.  *   This routine recalculates the checksum for a block, and updates it in
  950.  *   word 5.  The sum of all the words in a block must be 0.
  951.  */
  952. recheck(w)
  953. register long *w ;
  954. {
  955.    register int i ;
  956.    register long cs ;
  957.  
  958.    cs = 0 ;
  959.    for (i=0; i<128; i++)
  960.       cs += w[i] ;
  961.    w[5] -= cs ;
  962. }
  963. /*
  964.  *   We simply DateStamp the creation date, modification date, and
  965.  *   rechecksum the block.
  966.  */
  967. updaterootblock(b)
  968. register int b ;
  969. {
  970.    DateStamp(buffers[b] + 420) ;
  971.    DateStamp(buffers[b] + 484) ;
  972.    recheck(buffers[b]) ;
  973. }
  974. /*
  975.  *   If we read from track 40, we write the name on the screen in
  976.  *   the string gadget supplied for that purpose.
  977.  */
  978. writename(b)
  979. int b ;
  980. {
  981.    int i = buffers[b][432] ;
  982.    int j ;
  983.  
  984.    RemoveGadget(window, &namegadg) ;
  985.    if (i > 31)
  986.       i = 31 ;
  987.    for (j=0; j<i; j++)
  988.       namebuf[j] = buffers[b][j + 433] ;
  989.    namebuf[j] = 0 ;
  990.    AddGadget(window, &namegadg, -1L) ;
  991.    RefreshGadgets(&namegadg, window, NULL) ;
  992. }
  993. /*
  994.  *   And finally, our main routine!  This thing is awfully long; they do
  995.  *   get that way sometimes, don't they?
  996.  */
  997. main(argc, argv)
  998. int argc ;
  999. char *argv[] ;
  1000. {
  1001.    int op ;
  1002.    register char *p, *q ;
  1003.    int seen = 0 ;
  1004.  
  1005.    output = (long)Output() ;
  1006. /*
  1007.  *   First, we parse the arguments.  Arguments allowed are documented at
  1008.  *   the top of this file.  If there is a dash as the first character of
  1009.  *   an argument, we ignore it, thus allowing Unix-style options.
  1010.  */
  1011.    while (argc > 1) {
  1012.       argc-- ;
  1013.       argv++ ;
  1014.       p = *argv ;
  1015.       if (argc > 1)
  1016.          q = argv[1] ;
  1017.       else
  1018.          q = "" ;
  1019.       if (*p == '-')
  1020.          p++ ;
  1021.       switch (upcase(*p)) {
  1022. /*
  1023.  *   If the argument starts with a D, or a number, it specifies a drive.
  1024.  *   Actually, it might be a `DRIVE' keyword, which would return a mask of
  1025.  *   0, so we check the return value for 0.
  1026.  */
  1027. case '0' : case '1' : case '2' : case '3' : case 'D' :
  1028.          dest = getmask(p) ;
  1029.          if (dest != 0) {
  1030.             if (! seen)
  1031.                source = dest ;
  1032.             seen = 1 ;
  1033.          }
  1034.          break ;
  1035. /*
  1036.  *   The from keyword sets the source . . .
  1037.  */
  1038. case 'F' :
  1039.          argc-- ;
  1040.          argv++ ;
  1041.          source = getmask(q) ;
  1042.          seen = 1 ;
  1043.          break ;
  1044. /*
  1045.  *   The to keyword sets the destination . . .
  1046.  */
  1047. case 'T' :
  1048.          argc-- ;
  1049.          argv++ ;
  1050.          dest = getmask(q) ;
  1051.          break ;
  1052. /*
  1053.  *   This could either be a `nobuffer', `noverify', or `name' keyword.
  1054.  *   We check for any of these.
  1055.  */
  1056. case 'N' :
  1057.          if (p[1]=='o' || p[1]=='O') {
  1058.             if (p[2]=='v' || p[2]=='V')
  1059.                verify = 0 ;
  1060.             else if (p[2]=='b' || p[2]=='B')
  1061.                buffer = 0 ;
  1062.             else goto errorarg ;
  1063.          } else {
  1064.             if (strlen(q) > 30)
  1065.                q[30] = 0 ;
  1066.             strcpy(namebuf, q) ;
  1067.             source = 0 ;
  1068.             argc-- ;
  1069.             argv++ ;
  1070.          }
  1071.          break ;
  1072. /*
  1073.  *   Verify keyword is easy
  1074.  */
  1075. case 'V' :
  1076.          verify = 1 ;
  1077.          break ;
  1078. /*
  1079.  *   As is the buffer keyword.
  1080.  */
  1081. case 'B' :
  1082.          buffer = 1 ;
  1083.          break ;
  1084. /*
  1085.  *   We go ahead and print an error message if we didn't understand an
  1086.  *   option.
  1087.  */
  1088. default:
  1089. errorarg:
  1090.          if (output) {
  1091.             Write(output, "Unknown option ", 15L) ;
  1092.             Write(output, p, (long)strlen(p)) ;
  1093.             Write(output, "\n", 1L) ;
  1094.          }
  1095.       }
  1096.    }
  1097. /*
  1098.  *   Up to this point, the source has been a mask.  Now we turn it into an
  1099.  *   integer.
  1100.  */
  1101.    if (source & 8)
  1102.       source = 3 ;
  1103.    else if (source & 4)
  1104.       source = 2 ;
  1105.    else if (source & 2)
  1106.       source = 1 ;
  1107.    else if (source & 1)
  1108.       source = 0 ;
  1109.    else
  1110.       source = -1 ;
  1111. /*
  1112.  *   We initialize a few gadgets based on the parameters the user selected.
  1113.  */
  1114.    if (verify) {
  1115.       verifygadg.GadgetText = &verifyontext ;
  1116.    } else {
  1117.       verifygadg.GadgetText = &verifyofftext ;
  1118.    }
  1119.    if (buffer) {
  1120.       buffergadg.GadgetText = &bufferontext ;
  1121.       hibuf = 80 ;
  1122.    } else {
  1123.       buffergadg.GadgetText = &bufferofftext ;
  1124.       hibuf = 1 ;
  1125.    }
  1126. /*
  1127.  *   And now we try and open things up!  First intuition, then graphics,
  1128.  *   then our window and an I/O port.  If any of these fail, we simply
  1129.  *   exit.
  1130.  */
  1131.    if ((IntuitionBase = (struct IntuitionBase *)OpenLibrary(
  1132.          "intuition.library",33L))!=NULL &&
  1133.        (GfxBase = (struct GfxBase *)OpenLibrary(
  1134.          "graphics.library",0L))!=NULL &&
  1135.          (window=OpenWindow(&newwindow))!=NULL &&
  1136.        (port=CreatePort(0L, 0L)) &&
  1137.        (gpacket=(struct StandardPacket *)AllocMem(
  1138.                (long)sizeof(struct StandardPacket),MEMF_PUBLIC|MEMF_CLEAR))) {
  1139. /*
  1140.  *   We draw the lines between what the user has selected, and enter into
  1141.  *   our main command loop.  Then, we wait for a message.  After getting one,
  1142.  *   we drop into a case statement keying off what message it was.
  1143.  */
  1144.       redrawlines() ;
  1145.       getbuffers(hibuf) ;
  1146.       while (1) {
  1147.          if (havedisk)
  1148.             turnon(&againgadg) ;
  1149.          else
  1150.             turnoff(&againgadg) ;
  1151.          turnoff(&retrygadg) ;
  1152.          turnon(&gogadg) ;
  1153.          turnon(&verifygadg) ;
  1154.          turnon(&buffergadg) ;
  1155.          if (source == -1)
  1156.             turnon(&namegadg) ;
  1157.          else
  1158.             turnoff(&namegadg) ;
  1159.          RefreshGadgets(window->FirstGadget, window, NULL) ;
  1160.          while ((message = (struct IntuiMessage *)
  1161.                         GetMsg(window->UserPort))==NULL)
  1162.             WaitPort(window->UserPort) ;
  1163.          op = getop() ;
  1164.          switch(op) {
  1165. /*
  1166.  *   A shift-0, shift-1, shift-2, or shift-3 toggles the appropriate bit
  1167.  *   in the destination, and redraws the lines.  A 0, 1, 2, or 3 sets the
  1168.  *   source to that drive, and continues.
  1169.  */
  1170. case ')' :
  1171.             dest ^= 1 ;
  1172.             goto redrawem ;
  1173. case '!' :
  1174.             dest ^= 2 ;
  1175.             goto redrawem ;
  1176. case '@' :
  1177.             dest ^= 4 ;
  1178.             goto redrawem ;
  1179. case '#' :
  1180.             dest ^= 8 ;
  1181.             goto redrawem ;
  1182. case '0' : case '1' : case '2' : case '3' :
  1183.             source = op - '0' ;
  1184. redrawem :
  1185.             redrawlines() ;
  1186.             break ;
  1187. /*
  1188.  *   If verify is selected, we toggle the state of the verify flag, and
  1189.  *   update the gadget to reflect this state.
  1190.  */
  1191. case 'V' :
  1192.             verify = ! verify ;
  1193.             RemoveGadget(window, &verifygadg) ;
  1194.             if (verify) {
  1195.                verifygadg.GadgetText = &verifyontext ;
  1196.             } else {
  1197.                verifygadg.GadgetText = &verifyofftext ;
  1198.             }
  1199.             AddGadget(window, &verifygadg, -1L) ;
  1200.             RefreshGadgets(&verifygadg, window, NULL) ;
  1201.             break ;
  1202. /*
  1203.  *   The buffer option does essentially the same thing, only for buffering.
  1204.  */
  1205. case 'B' :
  1206.             buffer = ! buffer ;
  1207.             RemoveGadget(window, &buffergadg) ;
  1208.             if (buffer) {
  1209.                buffergadg.GadgetText = &bufferontext ;
  1210.                getbuffers(80) ;
  1211.             } else {
  1212.                buffergadg.GadgetText = &bufferofftext ;
  1213.                getbuffers(1) ;
  1214.             }
  1215.             havedisk = 0 ;
  1216.             AddGadget(window, &buffergadg, -1L) ;
  1217.             RefreshGadgets(&buffergadg, window, NULL) ;
  1218.             break ;
  1219. /*
  1220.  *   If the user selects `format', then we set the source appropriately and
  1221.  *   redraw the lines.  Of course, we no longer have a disk, as when the
  1222.  *   format is executed, it will destroy buffer 0.
  1223.  */
  1224. case 'F' :
  1225.             source = -1 ;
  1226.             havedisk = 0 ;
  1227.             goto redrawem ;
  1228. /*
  1229.  *   The name gadget, if invoked from the keyboard with the `n' key,
  1230.  *   Activates the string gadget.
  1231.  */
  1232. case 'N' :
  1233.             if (gadad == NULL && source == -1) {
  1234.                ActivateGadget(&namegadg, window, NULL) ;
  1235.             }
  1236.             break ;
  1237. /*
  1238.  *   The `G' gadget, or carriage return, starts us on our merry way.
  1239.  */
  1240. case 'G' : case 10 : case 32 : case 13 :
  1241.             goforit(0) ;
  1242.             break ;
  1243. /*
  1244.  *   The `A' gadget insures that we have a disk first, otherwise it
  1245.  *   complains.
  1246.  */
  1247. case 'A' :
  1248.             if (! havedisk) {
  1249.                while (error("No disk in memory")=='R') ;
  1250.             } else {
  1251.                goforit(1) ;
  1252.             }
  1253.             break ;
  1254. /*
  1255.  *   Retry is ignored in the main command loop, as there is nothing to
  1256.  *   retry!
  1257.  */
  1258. case 'R' :
  1259. /*
  1260.  *   Quit is handled at the bottom of the loop.
  1261.  */
  1262. case 'Q' :
  1263. /*
  1264.  *   As a default, we do nothing; not even an error message.
  1265.  */
  1266. default :
  1267.             break ;
  1268.          }
  1269. /*
  1270.  *   If the last gadget selected was 'Q', we exit.
  1271.  */
  1272.          if (op == 'Q')
  1273.             break ;
  1274.       }
  1275.    } else {
  1276.       error("! couldn't open window") ;
  1277.    }
  1278. /*
  1279.  *   Release memory and exit.
  1280.  */
  1281.    cleanup() ;
  1282. }
  1283.